﻿//-----------------------------------------------------------------------------
// 
// Yet another Text Log Writer
//
// Automatically:
//   - Thread safe
//   - UTC or Local
//   - Creates a log with unique file names derived from the name of the current exe
//   - If a file with a name is being used, adds a number to the name and tries again..
//   - Switches to a new log file at midnight.
//
//-----------------------------------------------------------------------------
namespace NHINDirect
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO;
    using System.Diagnostics;

    public class LogWriter : IDisposable
    {
        public const string Debug = "Debug";
        public const string Info = "Info";
        public const string Warning = "Warning";
        public const string Error = "Error";
        public const string Report = "Report";

        string m_folder;
        string m_filePrefix;
        string m_ext;
        bool m_utc;
        DateTime m_timestamp;
        StreamWriter m_writer;

        public LogWriter(string folderPath)
            : this(folderPath, false)
        {
        }

        public LogWriter(string folderPath, bool utc)
            : this(folderPath, null, null, utc)
        {
        }

        public LogWriter(string folderPath, string filePrefix, string ext, bool utc)
        {
            Directory.CreateDirectory(folderPath);
            if (string.IsNullOrEmpty(filePrefix))
            {
                filePrefix = this.CreateFilePrefix();
            }
            if (folderPath[folderPath.Length - 1] == '\\')
            {
                folderPath = folderPath.Substring(0, folderPath.Length - 1);
            }
            if (string.IsNullOrEmpty(ext))
            {
                ext = "log";
            }
            this.m_folder = folderPath;
            this.m_filePrefix = filePrefix;
            this.m_ext = ext;
            this.m_utc = utc;
            this.m_timestamp = DateTime.MinValue;
        }
        
        public string FolderPath
        {
            get
            {
                return this.m_folder;
            }
        }
        
        DateTime Now
        {
            get
            {
                return this.m_utc ? DateTime.UtcNow : DateTime.Now;
            }
        }

        public void Write(string category, object obj)
        {
            this.Write(category, obj.ToString());
        }

        public void WriteError(Exception error)
        {
            this.Write(LogWriter.Error, "Exception={0}", error);
        }

        public void WriteError(string error)
        {
            this.Write(LogWriter.Error, "Error={0}", error);
        }

        public void Write(string format, params object[] args)
        {
            this.Write(LogWriter.Info, format, args);
        }

        public void Write(string category, string format, params object[] args)
        {
            if (string.IsNullOrEmpty(category) || string.IsNullOrEmpty(format))
            {
                throw new ArgumentException();
            }
            lock (this)
            {
                DateTime now = this.Now;
                string nowString = now.ToString("yyyy-MM-dd hh:mm:ss.ffff");
                string message = format;
                if (args != null && args.Length > 0)
                {
                    message = string.Format(format, args);
                }
                StreamWriter writer = this.EnsureWriter(now);
                writer.WriteLine("{0},{1},{2}", nowString, category, message);
                writer.Flush();
            }
        }

        StreamWriter EnsureWriter(DateTime now)
        {
            if (now.Day != this.m_timestamp.Day || now.Month != this.m_timestamp.Month || now.Year != this.m_timestamp.Year)
            {
                Stream newStream = null;
                try
                {
                    newStream = this.OpenFileStream(now);
                    this.CloseWriter();
                    this.m_writer = new StreamWriter(newStream);
                    this.m_timestamp = now;
                    newStream = null;
                }
                catch
                {
                }
                finally
                {
                    if (newStream != null)
                    {
                        newStream.Close();
                    }
                }
            }

            return this.m_writer;
        }

        void CloseWriter()
        {
            if (this.m_writer != null)
            {
                this.m_writer.Flush();
                this.m_writer.Close();
                this.m_writer = null;
            }
        }

        Stream OpenFileStream(DateTime now)
        {
            int id = -1;
            int maxid = 1024;
            Stream fileStream = null;
            do
            {
                id++;
                string filePath;
                if (id == 0)
                {
                    filePath = string.Format("{0}\\{1}_{2}.{3}", this.m_folder, this.m_filePrefix, now.ToString("yyyyMMdd"), this.m_ext);
                }
                else
                {
                    filePath = string.Format("{0}\\{1}_{2}_{3}.{4}", this.m_folder, this.m_filePrefix, now.ToString("yyyyMMdd"), id, this.m_ext);
                }
                fileStream = null;
                try
                {
                    fileStream = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
                    fileStream.Seek(0, SeekOrigin.End);
                    return fileStream;
                }
                catch
                {
                    // Somebody already has this file open
                    if (fileStream != null)
                    {
                        fileStream.Close();
                        fileStream = null;
                    }
                }
                finally
                {
                }
            }
            while (id < maxid);

            throw new InvalidOperationException("Too many open log files");
        }

        string CreateFilePrefix()
        {
            try
            {
                using (Process process = Process.GetCurrentProcess())
                {
                    return Path.GetFileNameWithoutExtension(process.MainModule.ModuleName);
                }
            }
            catch
            {
            }

            return "Log";
        }
        #region IDisposable Members

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
